package csel.model;

import java.util.EnumMap;
import java.util.Map;

import org.apache.log4j.Logger;

public class CharacterStatsHolder implements java.io.Serializable, Observable<CharacterStats>
{
    /**
     * Change this if we change its structure.
     */
    private static final long serialVersionUID = 6L;
    
    private static final Logger logger = Logger.getLogger("csel.model");

	private final ObsHelper<CharacterStats> myStatsObservers;
	private final EnumMap<CharacterStats, Integer> stats;
	private final EnumMap<DamageTypes, Integer> resistances;

	public CharacterStatsHolder()
	{
		myStatsObservers = new ObsHelper<CharacterStats>(this);
		
		stats = new EnumMap<CharacterStats, Integer>(CharacterStats.class);
		resistances = new EnumMap<DamageTypes, Integer>(DamageTypes.class);

		//needed so that calculateDerived doesn't return null pointer exception since
		//setStat automatically calls calculateDerived
		stats.put(CharacterStats.Agility, 0);
		stats.put(CharacterStats.Strength, 0);
		stats.put(CharacterStats.Intellect, 0);
		stats.put(CharacterStats.Hardiness, 0);
		stats.put(CharacterStats.Experience, 0);
		stats.put(CharacterStats.Speed, 0);
		stats.put(CharacterStats.Damage, 0);
		stats.put(CharacterStats.ManaUsed, 0);

		calculateDeriveds();
	}

	public int getStat(CharacterStats stat)
	{
		Integer statval = stats.get(stat);
		if(statval!=null)
		{
			return statval.intValue();
		}
		else
		{
			return(0);
		}
	}
	public int getResistance(DamageTypes dt)
	{
		Integer statval = resistances.get(dt);
		if(statval!=null)
		{
			return statval.intValue();
		}
		else
		{
			return(0);
		}
	}
	public Map<CharacterStats, Integer> getStats()
	{
		return stats.clone();
	}
	public Map<DamageTypes, Integer> getResistances()
	{
		return resistances.clone();
	}

	public void setStat(CharacterStats stat, int value)
	{
		stats.put(stat, value);
		notifyStatsObservers(stat);
		calculateDeriveds();
	}
	public void setResistance(DamageTypes dt, int value)
	{
		resistances.put(dt, value);
	}

	private void calculateDeriveds()
	{
		if(getStat(CharacterStats.Damage)<0)
		{
			stats.put(CharacterStats.Damage, 0);
		}
		if(getStat(CharacterStats.ManaUsed)<0)
		{
			stats.put(CharacterStats.ManaUsed, 0);
		}
		Integer level = getLevel();

		Integer maxLife = level * getStat(CharacterStats.Hardiness);
		Integer damage = getStat(CharacterStats.Damage);
		
		if(damage >= maxLife)
		{
			Integer pastLives = getStat(CharacterStats.Lives);
			if(pastLives == 0)
			{

			}
			else
			{
				stats.put(CharacterStats.Lives, pastLives-1);
				stats.put(CharacterStats.Damage, 0);
			}
			notifyStatsObservers(CharacterStats.Lives);
		}
		Integer currentLife = maxLife - getStat(CharacterStats.Damage);
		Integer maxMana = level * getStat(CharacterStats.Intellect);
		Integer currentMana = maxMana - getStat(CharacterStats.ManaUsed);

		Integer offense = level * getStat(CharacterStats.Strength);
		Integer defense = level * getStat(CharacterStats.Agility);
		Integer armor = getStat(CharacterStats.Hardiness);

		
		if(stats.put(CharacterStats.Level, level) != level)
		{
			notifyStatsObservers(CharacterStats.Level);
		}
		if(stats.put(CharacterStats.MaxLife, maxLife) != maxLife)
		{
			notifyStatsObservers(CharacterStats.MaxLife);
		}
		if(stats.put(CharacterStats.CurrentLife, currentLife) != currentLife)
		{
			notifyStatsObservers(CharacterStats.CurrentLife);
		}
		if(stats.put(CharacterStats.MaxMana, maxMana) != maxMana)
		{
			notifyStatsObservers(CharacterStats.MaxMana);
		}
		if(stats.put(CharacterStats.CurrentMana, currentMana) != currentMana)
		{
			notifyStatsObservers(CharacterStats.CurrentMana);
		}
		if(stats.put(CharacterStats.Offense, offense) != offense)
		{
			notifyStatsObservers(CharacterStats.Offense);
		}
		if(stats.put(CharacterStats.Defense, defense) != defense)
		{
			notifyStatsObservers(CharacterStats.Defense);
		}
		if(stats.put(CharacterStats.Armor, armor) != armor)
		{
			notifyStatsObservers(CharacterStats.Armor);
		}
	}
	
	public Integer getLevel()
	{
		return((int)(Math.sqrt(getStat(CharacterStats.Experience)/1000) + 1));
	}
	
	public void levelUp()
	{
		Integer level = getLevel();
		while(getLevel()== level)
		{
			stats.put(CharacterStats.Experience, getStat(CharacterStats.Experience)+5);
			notifyStatsObservers(CharacterStats.Experience);
		}
		calculateDeriveds();

	}
	
	public void addObserver(Observer<CharacterStats> lo)
	{
		myStatsObservers.add(lo);
	}

	public boolean removeObserver(Observer<CharacterStats> o)
	{
		return myStatsObservers.remove(o);
	}
	
	public void notifyStatsObservers(CharacterStats cs)
	{
		myStatsObservers.notifyO(cs);
	}
}
